Extracting OLE embedded images from emails in Outlook#

While it seemed a simple requirement, saving all attachments from emails in Outlook to disk proved to be challenging - to say the least. Using the Outlook Object Model, it's quite easy to enumerate all emails in a folder, look at their attachments and call the SaveAsFile method on them. However, for OLE-type attachments (typically images), this throws a COMException saying: "Outlook cannot do this action on this type of attachment". While looking for alternatives or workarounds, I found nothing but confirmation that this is indeed not an easy task - even from Dmitry Streblechenko, Outlook MVP and creator of the excellent and very affordable Outlook Redemption library: "If you mean embedded graphics objects in the RTF messages, there is not much you can do [...] You can look at the storage itself to figure that out, but I've never tried that".

Ultimately, after lots of trial and error, I did manage to find a fairly easy way to save these OLE embedded images by (mis)using the clipboard. Basically, I retrieve the attachment’s IStorage OLE interface (available through Redemption) and call OleLoad on it to have OLE load the contents and retrieve an IDataObject. The magic trick is to place that IDataObject on the clipboard and retrieve the actual image from the clipboard (so that the clipboard itself handles the nasty OLE details).

Great success! At least for a moment. That already worked for Device Independent Bitmaps, but Outlook also uses Enhanced Metafiles (wmf) and apparently there is a problem with the .NET Framework when it comes to handling Enhanced Metafiles from the clipboard. So I needed some additional COM interop to handle these Enhanced Metafiles as well, which made the code slightly more difficult to read but fortunately still effective. The trick here is to make sure you have a valid handle to pass to OpenClipboard. Because I didn't have access to a form or other type of existing control, I just created a dummy button and used its handle.

Finally, be aware that to access OLE functionality, you need a Single Threaded Apartment (STA) model. Of course I was in an MTA context, so from there I launched a new thread which I put to STA - after that, everything was golden.

Below is the full code using Redemption Data Objects (RDO), hopefully this will save other people a few hours in trying to achieve the same thing...

public static class Program
{
  public static void Main()
  {
    // Calling code should always ensure to be in STA.
    Thread staThread = new Thread(new ThreadStart(SaveOutlookAttachments));
    staThread.SetApartmentState(ApartmentState.STA);
    staThread.Start();
  }

  private static void SaveOutlookAttachments()
  {
    RDOSession session = new RDOSessionClass();
    RDOFolder inbox = session.GetDefaultFolder(rdoDefaultFolders.olFolderInbox);
    string attachmentRootPath = AppDomain.CurrentDomain.BaseDirectory;
    foreach (RDOMail mail in inbox.Items)
    {
      foreach (RDOAttachment attachment in mail.Attachments)
      {
        if (attachment.Type == rdoAttachmentType.olOLE)
        {
          // We don't have a filename for this type of attachment, create a unique one.
          string filename = Guid.NewGuid().ToString() + ".png";
          string attachmentPath = Path.Combine(attachmentRootPath, filename);
          // We assume here that only images will be stored as OLE attachments.
          // We save them as PNG to keep the file size small.
          SaveOleImageAttachment(attachment, attachmentPath, ImageFormat.Png);
        }
        else
        {
          string attachmentPath = Path.Combine(attachmentRootPath, attachment.FileName);
          attachment.SaveAsFile(attachmentPath);
        }
      }
    }
  }

  private static void SaveOleImageAttachment(RDOAttachment attachment, string filePath, ImageFormat format)
  {
    // Use the OLE storage interface to load the OLE document into a DataObject.
    IStorage oleStorage = (IStorage)attachment.OleStorage;
    object oleDataObject;
    OleLoad(oleStorage, ref IDataObjectGuid, null, out oleDataObject);

    // Copy the OLE DataObject to the clipboard so it can handle the internals.
    Clipboard.SetDataObject(oleDataObject, false);

    // Try to retrieve an image back from the clipboard.
    if (Clipboard.ContainsData(DataFormats.EnhancedMetafile))
    {
      // Enhanced Metafiles cannot be handled natively from .NET.
      // Use the Clipboard directly to retrieve the data.

      // We need a valid handle, otherwise this won't work.
      Button dummy = new Button();
      if (OpenClipboard(dummy.Handle))
      {
        try
        {
          if (IsClipboardFormatAvailable(CF_ENHMETAFILE))
          {
            IntPtr metafileData = GetClipboardData(CF_ENHMETAFILE);
            if (metafileData != IntPtr.Zero)
            {
              using (Metafile metafile = new Metafile(metafileData, true))
              {
                metafile.Save(filePath, format);
              }
            }
          }
        }
        finally
        {
          EmptyClipboard();
          CloseClipboard();
        }
      }
    }
    else if (Clipboard.ContainsImage())
    {
      using (Image image = Clipboard.GetImage())
      {
        if (image != null)
        {
          image.Save(filePath, format);
        }
      }
    }
  }

  [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
  private static extern bool OpenClipboard(IntPtr hWndNewOwner);
  [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
  private static extern bool CloseClipboard();
  [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
  private static extern IntPtr GetClipboardData(uint format);
  [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
  private static extern bool IsClipboardFormatAvailable(uint format);
  [DllImport("user32.dll")]
  private static extern bool EmptyClipboard();
  [DllImport("ole32.dll")]
  private static extern int OleLoad(IStorage pStg, [In] ref Guid riid, IOleClientSite pClientSite, [MarshalAs(UnmanagedType.IUnknown)] out object ppvObj);

  private static Guid IDataObjectGuid = new Guid("0000010E00000000C000000000000046");
  private const uint CF_ENHMETAFILE = 14;
}
Blog | General | Programming | .NET | Samples
Monday, June 02, 2008 12:18:29 PM (Romance Standard Time, UTC+01:00) #    Comments [2]  | 

 

InfoPathHelper v1.1: add offline support to InfoPath!#

It's been a while since I released the original version of InfoPathHelper, a small reusable .NET library you can use to add offline support to InfoPath forms. I still think it's a nice little utility, and it makes me proud that even the InfoPath folks think enough of it to link to it on their Office Developer Center!

Unfortunately, I've always found it hard to really promote it since there was this one nasty bug that didn't make me very proud: it would sometimes blow up when caching data from a Web Service. But today, I'm happy to announce that I've fixed that bug and added a few other tweaks as well so I believe it's now a fully functional product!

I'll repeat the elevator pitch from my original post here just to round up what it does:

InfoPathHelper is a small reusable .NET library you can use to add offline support to InfoPath forms. That's right, you're finally able to make InfoPath a real smart client that is offline capable of submitting forms and querying data sources, without any extra security requirements whatsoever.

For the end user, the form will appear to continue working when the machine is offline or when the target you're submitting your form to is unavailable (a Web Service, a Sharepoint site, ...). The next time the machine goes online, any pending requests will be submitted. The same goes for querying data sources (files on a network share, Web Services, ...): if they're not available, the data will be taken from the local cache until the next time it can be refreshed.

For a developer using the InfoPath 2003 Toolkit for Visual Studio .NET, it boils down to inheriting from the CachingInfoPathForm base class, changing your submittal options to use form code, deleting the messy generated boilerplate code (you get cleaner properties and events for free in stead) and calling a few methods depending on your needs. The following example shows how to enable offline support for a form that has a submit data adapter named "WebServiceSubmit" and a data object named "Countries".

public class MyCachingForm : CachingInfoPathForm
{
    [InfoPathEventHandler(EventType=InfoPathEventType.OnSubmitRequest)]
    public void OnSubmitRequest(DocReturnEvent e)
    {
        // Attempt to submit the request and return a value to indicate if it worked.
        e.ReturnStatus = ProcessSubmitRequest( "WebServiceSubmit" );
    }

    [InfoPathEventHandler(EventType=InfoPathEventType.OnLoad)]
    public void OnLoad(DocReturnEvent e)
    {
        // Attempt to submit any cached requests and query a data object.
        SubmitCachedRequests( "WebServiceSubmit" );
        QueryDataObject( "Countries" );
    }
}

If you're wondering where the cached data is stored without needing any extra security requirements: welcome to the magical world of Isolated Storage :-)

Changes in v1.1:

  • Strong-named the assembly. This is needed for the offline caching to work properly, otherwise a published form would apparently get a new directory in the Isolated Storage cache each time the form is run.
  • Added the AllowPartiallyTrustedCallers attribute to the assembly. This is needed to allow the now-strong-named code to run within InfoPath.
  • Fixed the bug where a "This DOM cannot be loaded twice" COM exception would be thrown when caching data from a Web Service.

Thanks to David Downs at Microsoft for reporting and solving the issue with the isolated storage, and thanks to both David Downs and John Boesen for coming up with part of the solution for the bug with the Web Service!

Enough talk, here's the goodies:

The JelleDruyts.InfoPathHelper 1.1 library and documentation (125,12 KB)
The JelleDruyts.InfoPathHelper 1.1 library, source code and documentation (244,17 KB)

Tuesday, May 09, 2006 7:16:12 PM (Romance Standard Time, UTC+01:00) #    Comments [5]  | 

 

Faking a current HttpContext#

I was recently looking for a way to unit test a piece of security code that would retrieve the User principal from the current HttpContext in an ASP.NET scenario. Now it was just a plain unit test, not a web test, so I didn't actually have an HttpContext. But how hard could it be to cook one up, right?

So I first tried to set the HttpContext.Current property, but IntelliSense popped up to say: "Gets the HttpContext object for the current HTTP request". So I figure it's a read-only property (because the phrase starts with "Gets...", where read-write properties typically start with "Gets or sets...") and I can't do it directly.

Then I found a post by Steve Maine that seemed to allow setting the current HttpContext by injecting it into the messaging CallContext of .NET Remoting. While that looks like a bit of black magic, I'm never too shy to throw in some snake blood and salamander eyes if it makes my code compile so I tried that, but it didn't seem to work right away. It did, however, provide me with a good way of creating a dummy HttpContext in the first place, which I also needed to do:

SimpleWorkerRequest request = new SimpleWorkerRequest("/dummy", @"c:\inetpub\wwwroot\dummy", "dummy.html", null, new StringWriter());
HttpContext context = new HttpContext(request);

So we just create a dummy worker request that the HttpContext requires and we pass it into the constructor.

Because the black magic wasn't working for me, I looked at the HttpContext class in my favorite .NET tool of all time, and quickly realized that the property was settable anyway [1]. So I guess that salamander had a lucky day:

HttpContext.Current = context;

And we're done. A valid current HttpContext for use in unit tests.

[1] Side comment: property documentation that doesn't follow the "Gets..." versus "Gets or sets..." pattern is pretty frustrating. It could have saved me quite some time if the documentation would just say "Gets or sets the HttpContext object for the current HTTP request"...

Blog | Programming | .NET | ASP.NET | Samples
Wednesday, April 05, 2006 10:07:31 AM (Romance Standard Time, UTC+01:00) #    Comments [2]  | 

 

Enterprise Library Caching without configuration#

I've been using Enterprise Library 2.0 quite a lot lately, and although the configuration part is truly excellent and easy to use, sometimes you just want to use some block without going through configuration. In my case, I wanted to use a cache somewhere in a piece of framework code without requiring users of that code to add anything "magic" to their application configuration to make it work. So with a little help of Brian Button, I cooked up the following code to create a standard CacheManager with an in-memory backing store:

DictionaryConfigurationSource internalConfigurationSource = new DictionaryConfigurationSource();
CacheManagerSettings settings = new CacheManagerSettings();
internalConfigurationSource.Add(CacheManagerSettings.SectionName, settings);
CacheStorageData storageConfig = new CacheStorageData("Null Storage", typeof(NullBackingStore));
settings.BackingStores.Add(storageConfig);
CacheManagerData cacheManagerConfig = new CacheManagerData("CustomCache", 60, 1000, 10, storageConfig.Name);
settings.CacheManagers.Add(cacheManagerConfig);
settings.DefaultCacheManager = cacheManagerConfig.Name;
CacheManagerFactory cacheFactory = new CacheManagerFactory(internalConfigurationSource);
CacheManager customCache = cacheFactory.CreateDefault();

So this piece of code in fact creates the configuration objects directly and passes them on to the CacheManagerFactory to use so it doesn't go about reading any configuration files. Pretty simple once you get the hang of it, and pretty powerful for the cases you want to use Enterprise Library "behind the scenes".

Check out Brian's blog for an example on Exception Handling without using an external configuration file. By the way: thanks for all your work on EntLib and all the best in your career move, Brian!

Blog | Programming | .NET | EntLib | Samples
Tuesday, March 28, 2006 9:06:35 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Xml Documenter Macro#

Although I've argued before that Typed DataSets aren't evil, there's one thing that bothers me a lot when dealing with them in a library project: the code that gets generated doesn't contain any XML comments. In itself, that isn't so bad (after all, which useful comments could be generated from an XSD schema?), but if the project is configured to emit an XML documentation file, you get a gazillion warnings for each member that lacks a comment. I like to keep my build warning-free to avoid "known harmless warnings" from hiding "new and important warnings" (if I can first get my stuff to build without errors, at least). You could go about and put comments on the generated file manually, but any change on the dataset's schema file will trigger the code to be regenerated so you've lost all your hard work. Hence I created the following quick and dirty solution: a Visual Studio macro that runs over all members and puts a placeholder comment (containing just the member's name) on top. Anytime the code gets regenerated, just run the macro and you're set. I'm sure this should be able to run as a post-build step or something too, but if you're lazy like me (and you're a programmer too, so you're lazy by definition), you'll already be happy you got this done.

Oh, and if you're really lazy, you can use this macro to document all your members automatically - although I'd add some random word-generator to make it look just a little more convincing (to fool the QA-guys at least).

So here's the source (with special thanks to the MSDN article on XML Comment Macros):

Imports EnvDTE
Imports System.Diagnostics
Imports System
Imports System.Xml
Imports System.IO
Imports System.Collections
Imports System.Text
Imports System.Text.RegularExpressions

Public Module XmlDocumenter

    Public Sub InsertGenericSummaries()

        SetCommentsRecursively(DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElements)

    End Sub

    Private Sub SetCommentsRecursively(ByVal elements As CodeElements)

        For Each element As CodeElement In elements
            ' Get a comment block.
            Dim doc As XmlDocument = GetCommentXml(element)
            Dim summaryTag As XmlElement = doc.SelectSingleNode("/doc/summary")
            If summaryTag Is Nothing Then
                ' Create <summary> tag
                summaryTag = doc.CreateElement("summary")
                summaryTag.InnerXml = element.Name
                doc.FirstChild.PrependChild(summaryTag)
            End If
            SetCommentXml(element, doc)

            ' Recurse over members.
            Try
                SetCommentsRecursively(element.Members)
            Catch ex As Exception
                ' Ignore exceptions (quick and dirty hack to skip elements that have no Members property)
            End Try
        Next

    End Sub

    ' Code borrowed from http://msdn.microsoft.com/msdnmag/issues/05/07/XMLComments/default.aspx
    Private Sub SetCommentXml(ByVal element As CodeElement, ByVal doc As XmlDocument)
        Dim sb As StringBuilder = New StringBuilder
        Dim sw As StringWriter = New StringWriter(sb)
        Dim xw As XmlTextWriter = New XmlTextWriter(sw)

        Try
            xw.Indentation = 1
            xw.IndentChar = Char.Parse(vbTab)
            xw.Formatting = Formatting.Indented

            Dim rootNode As XmlNode
            If element.Language = CodeModelLanguageConstants.vsCMLanguageVB Then
                rootNode = doc.FirstChild
            Else ' CSharp
                rootNode = doc
            End If

            rootNode.WriteContentTo(xw)
            Try
                element.DocComment = sb.ToString()
            Catch ex As Exception
                ' Ignore
            End Try
        Finally
            xw.Close()
            sw.Close()
        End Try
    End Sub

    ' Code borrowed from http://msdn.microsoft.com/msdnmag/issues/05/07/XMLComments/default.aspx
    Private Function GetCommentXml(ByVal element As CodeElement) As XmlDocument

        Dim doc As XmlDocument = New XmlDocument

        Dim xmlStr As String = element.DocComment
        If Not xmlStr Is Nothing AndAlso xmlStr.Trim() <> String.Empty Then
            If xmlStr.StartsWith("<doc>") = False Then
                ' Ensure single root node
                xmlStr = "<doc>" & xmlStr + "</doc>"
            End If
            xmlStr = Regex.Replace(xmlStr, "^(\s*<doc>\s*){2,}", "<doc>")
            xmlStr = Regex.Replace(xmlStr, "(\s*<\/doc>\s*){2,}$", "</doc>")
            doc.LoadXml(xmlStr)
        Else
            Dim rootTag = doc.CreateElement("doc")
            doc.AppendChild(rootTag)
        End If

        Return doc
    End Function

End Module

Easy, effective, and ugly. Use at your own risk, it might eat your lunch sandwich.

Blog | Programming | .NET | VS.NET | Samples
Monday, January 16, 2006 8:49:00 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

A Generic Singleton Implementation#

The Singleton design pattern is one of the most widely known patterns in the software industry. In .NET 2.0, it's easy to provide a generic implementation of this pattern so you won't ever have to write the code that makes it happen again. Even though there's not much code involved, it can be tricky to get right if you consider thread safety and laziness of instantiation.

If you use the class below, accessing a singleton of any class is as simple as calling Singleton<Foo>.Instance. The Foo type only needs to support a default constructor and never worry about the details of the singleton itself. Here's the implementation of the generic Singleton class:

/// <summary>
/// Provides a global point of access to a single instance of a given class.
/// </summary>
/// <typeparam name="T">The type to provide a singleton instance for.</typeparam>
/// <remarks>
/// <para>
/// The singleton instance can be accessed through a static property,
/// where the type of the singleton is passed as a generic type parameter.
/// Subsequent requests for the instance will yield the same class instance.
/// </para>
/// <note>
/// The singleton is thread-safe and lazy (i.e. it is created when the instance
/// is first requested).
/// </note>
/// </remarks>
/// <example>
/// The following example gets a singleton instance of a <c>Foo</c> class:
/// <code>
/// Foo singleInstance = Singleton<Foo>.Instance;
/// </code>
/// </example>
public static class Singleton<T> where T : new()
{
    /// <summary>
    /// Gets the singleton instance.
    /// </summary>
    public static T Instance
    {
        get
        {
            // Thread safe, lazy implementation of the singleton pattern.
            // See http://www.yoda.arachsys.com/csharp/singleton.html
            // for the full story.
            return SingletonInternal.instance;
        }
    }

    private class SingletonInternal
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit.
        static SingletonInternal()
        {
        }

        internal static readonly T instance = new T();
    }
}

Note the implementation using a private class with an explicit static constructor, this is to provide both the thread safety as the lazy instantiation. More details can be found in the "Implementing the Singleton Pattern in C#" article by Jon Skeet.

Monday, November 07, 2005 7:11:50 PM (Romance Standard Time, UTC+01:00) #    Comments [5]  | 

 

Checking for .NET 1.1 Service Pack 1 in an MSI#

If you're using a Setup Project to build an MSI installer for your project inside Visual Studio .NET, there are quite some options that allow you to control the installation process. One of the properties you can configure are launch conditions, which allow your installer to check some registry values, installed programs or files to determine if the application can be installed at all.

Now there are a number of cumbersome methods to determine whether service packs are installed on the .NET Framework, but fortunately service pack discovery has been made much easier in .NET 1.1.

If your application requires .NET 1.1 with Service Pack 1 to be installed, you can check the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322 registry key for the SP value (which is a DWORD). If its value doesn't exist, you don't have .NET 1.1 installed; otherwise, it contains the installed Service Pack (or 0 if you don't have any service packs).

Now you can add a Search for the service pack in the installer and store it in a property named "SERVICEPACK":

In the actual launch condition, you can check that property to make sure the service pack has been installed, and point them to the official .NET 1.1 SP1 download page if that's not the case:

The tricky part is the Condition, which is set to NOT(SERVICEPACK="") AND NOT(SERVICEPACK>>"0"). Since MSI prefixes DWORD registry values with a # sign in front of them, the SERVICEPACK property will actually contain a "#0" or "#1" string, not the actual service pack number. So we first check if the property actually exists to check for .NET 1.1 (an empty string would be returned if the key didn't exist in the registry) and then we use the special substring operator >> to make sure the property doesn't end with "0". This way, it will work even for service packs later than SP1 (until SP10 arrives of course, but I don't expect that any time soon).

Now if anybody launches the installer without the required service pack, they will get the following screen prompting them to download it:

Blog | Programming | .NET | VS.NET | Samples
Saturday, April 09, 2005 2:08:04 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Indigo March CTP "Hello World" Server & Client#

Yes! I just got my first Indigo sample up and running!

I started off with a clean Windows XP SP2 installation, and I just installed the March CTP of Avalon/Indigo/WinFX (build 2.0.50110) without anything else - no Visual Studio .NET, no IIS, no nothing - so it's definitely back to basics here while I'm downloading the February CTP of Visual Studio .NET 2005... On the other hand, it's quite a lot of fun to just try and get anything working without any major productivity booster such as Visual Studio, IntelliSense or any form of actual documentation installed. For now, I'll consider notepad and ildasm.exe my best friends in the Indigo space. Long time no see, welcome back guys :-)

The first thing to try was to get any of the prepackaged samples working. I unzipped the AllSamples file in the "C:\Program Files\Microsoft SDKs\WinFX\samples" directory and seeing I had no Visual Studio installed I just tried to run MSBuild on the solution files. Although I think this should work, it was complaining to me about not being able to copy some files or something. Weird.

I then decided to take Clemens Vasters' Indigo Hello World sample and see what I could make of that. I just copied it into a new file and ran the compiler on it:

csc /r:"C:\Program Files\Microsoft Indigo Preview\System.ServiceModel.dll" IndigoHelloWorld.cs

Unfortunately, I got an error straight away: the BasicProfileHttpBinding class wasn't found. So here's ildasm to the rescue already. As it turns out, they recently must have renamed the class to BasicProfileBinding, so a simple change to the sourcefile fixes the problem and we're all set and compiled. Step 1 complete! The server code is now just:

using System;
using System.ServiceModel;

namespace IndiHello
{
  [ServiceContract]
  public class Hello
  {
    [OperationContract]
    public string SayHello(string name)
    {
      return "Hello " + name;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      ServiceHost<Hello> host = new ServiceHost<Hello>(new Uri("http://localhost/hello"));
      host.AddEndpoint(typeof(Hello), new BasicProfileBinding(), "ep");
      host.Open();
      Console.WriteLine("Press ENTER to quit");
      Console.ReadLine();
      host.Close();
    }
  }
}

So with my newborn Indigo service now up and running on my machine, I figured I should be able to make this do something, right? So because I couldn't really think of anything else, I just took the ASMX approach, which is to surf to the endpoint (http://localhost/hello/ep) and observe the magic: Indigo provides a very similar model to ASMX with an informative html page if you access the endpoint via HTTP/GET and is so kind as to give a hint what you could do next. Not only does it tell you how to generate a client proxy for the service, but it also gives some basic info about how to use the proxy afterwards.

So off I am building a client proxy using svcutil.exe (think wsdl.exe for Indigo) on the wsdl:

"C:\Program Files\Microsoft Indigo Preview\svcutil.exe" http://localhost/hello/ep?wsdl

This just creates a proxy class in a file named tempuri.org.cs (since I haven't changed the default namespace, it just takes the tempuri.org namespace as the base name) with the contract interface and some kind of channel interface (I'm not sure what that does yet but I'll find out sooner or later):

[System.ServiceModel.ServiceContractAttribute()]
public interface Hello
{
  [System.ServiceModel.OperationContractAttribute(Action=http://tempuri.org/Hello/SayHello, ReplyAction="http://tempuri.org/Hello/SayHelloResponse")]
  [return: System.ServiceModel.MessageBodyAttribute(Name="SayHelloResult", Namespace="http://tempuri.org/")]
  string SayHello([System.ServiceModel.MessageBodyAttribute(Namespace="http://tempuri.org/")] string name);
}

public interface HelloChannel : Hello, System.ServiceModel.IProxyChannel
{
}

public partial class HelloProxy : System.ServiceModel.ProxyBase<Hello>, Hello
{
  public HelloProxy()
  {
  }

  public HelloProxy(string configurationName) : 
    base(configurationName)
  {
  }

  public HelloProxy(System.ServiceModel.Binding binding) : 
    base(binding)
  {
  }

  public HelloProxy(System.ServiceModel.EndpointAddress address, System.ServiceModel.Binding binding) : 
    base(address, binding)
  {
  }

  public string SayHello(string name)
  {
    return base.InnerProxy.SayHello(name);
  }
}

Looks good. Next up: adding a test driver. Simple enough, the service's informative html page shows me how to create a simple console app that uses the proxy:

HelloProxy proxy = new HelloProxy();
string result = proxy.Hello("some data");
System.Console.WriteLine("The Hello service returned '" + result + "'");
proxy.Close();

So I just whipped that into the generated proxy class file, and tried to compile the bunch:

csc /out:Client.exe /r:"C:\Program Files\Microsoft Indigo Preview\System.ServiceModel.dll" tempuri.org.cs

Too bad, there seems to be a little bug in the way the service generates the html page (I bet you didn't spot that just now, neither did I), since the service operation isn't called "Hello", it's "SayHello". So a small change here as well, run the compiler again - which seems to give me an actual executable this time - and we're ready to try and run the client:

C:\IndigoTest>Client.exe
Unhandled Exception: System.InvalidOperationException: Could not find Channel element for contract Hello, Client, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
   at System.ServiceModel.Design.ConfigLoader.LoadChannelDescription(ChannelDescription channelDescription, ContractDescription contract, String configurationName, EndpointAddress address)
   at System.ServiceModel.Design.ChannelLoader..ctor(Type contractType, String configurationName, EndpointAddress address)
   at System.ServiceModel.ChannelFactory`1..ctor(String configurationName)
   at System.ServiceModel.ProxyBase`1..ctor()
   at Test.Main(String[] args)

Whoops, that's not good. Here's that Channel thing again. But then again, I didn't really provide an actual uri where the client could locate the service. So I decide to use one of the generated HelloProxy constructors that seems to make sense: the one with an EndpointAddress and Binding.

A quick look in Ildasm at the EndpointAddress class reveals a constructor that takes a uri string, so I'll just take that and then paste in the same binding as I used on the server side:

HelloProxy proxy = new HelloProxy(
    new System.ServiceModel.EndpointAddress( "http://localhost/hello/ep" ),
    new System.ServiceModel.BasicProfileBinding() );
string result = proxy.SayHello("some data");
System.Console.WriteLine("The Hello service returned '" + result + "'");
proxy.Close();

Throwing the compiler at the code creates me a new executable, so I run that and voila:

C:\IndigoTest>Client.exe
The Hello service returned 'Hello some data'

Wicked! My first Indigo client is talking to my first Indigo service over a basic profile HTTP binding, without even having IIS installed. Ain't that something...

Here are the files if you want to try it out yourself: IndigoHelloWorld.cs (service), tempuri.org.cs (client)

In retrospect, I could also have used the Indigo CTP SDK docs Don Box pointed at, but, well, it just didn't come to my mind. I was so happy to see ildasm again, I guess I didn't want to trade our new-found relationship for some long-distance relationship with a remote webserver ;-)

Now that I have this basic stuff out of the way, I can start digging in the articles, posts and documentation for Indigo to get a better hands-on view of the API.

Sunday, March 20, 2005 3:50:31 AM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

More on .NET Remoting and customErrors#

Yesterday, David Boschmans blogged about a problem with .NET Remoting and customErrors we noticed at the customer's site earlier this week. It basically boils down to the fact that the customErrors tag in the <system.web> part of the web.config file is being read by the remoting infrastructure (if you're hosting in IIS anyway) to determine whether or not to flow remote exceptions to the caller. I find this very bizarre, as does David. Ingo Rammer left a little note in the comments about the way Indigo will handle this situation, and I'd simply like to add my opinion to the matter here.

I think some confusion lies in the fact that .NET Remoting was designed to be as transparent as possible. I think of it this way: exceptions are part of the contract of a class; .NET Remoting is "only" a means to provide location transparency, so the remoting stack should always ensure that the exception is transferred to the client. There shouldn't even be a setting that influences this, as far as I'm concerned. Especially if that setting lives in the <system.web> section, since I expect that section to only affect ASP.NET sites. Up until now, that wasn't a section I would typically associate with the remoting stack.

The same configuration setting does exist in the <system.runtime.remoting> section as well though, where I can somewhat accept its existance, but certainly not its default value ("remoteOnly") since that can effectively change the behaviour if you scale to multiple servers as David states. That means that if you host your client (e.g. a web app) and server (remoting backend) on the same physical machine, everything will appear to work correctly since the exceptions are transferred locally. If you decide to scale out and move your remoting layer to another server, you'll see an unexpected RemotingException where your own application exception used to be and your app won't work as it used to do (if you're using structured exception handling anyway).

Now, concerning Ingo's comment... Indigo is different. I would actually expect Indigo to have a means to specify the way server-side exception information is exposed, because Indigo was never meant to be transparent - quite the opposite actually: "Boundaries Are Explicit", remember. And with Indigo, you don't typically "trust" the client, whereas with remoting you tend to have a lot more control over the caller so you would allow more trust about the exceptions being transferred. Anyway, I'm looking forward to seeing the new Indigo model in action, and how the [FaultContract] will interact with the various .NET and non-Microsoft clients out there...

Blog | Programming | .NET | ASP.NET | Quirks | Samples | Indigo
Thursday, March 10, 2005 9:24:55 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Binding the child rows of a DataRow#

Either I'm missing something or the ADO.NET team missed a fairly simple scenario... Imagine you have a single DataRow out of a table with a relation to another table. Now you want to bind a DataGrid (e.g. WinForms but that doesn't really matter here) to the child rows of that DataRow. Sounds easy, right?

And sure, there's a handy GetChildRows method that returns an array of child rows for a given relation. Unfortunately, binding a grid to an array will make it display the properties of the DataRow object, not the actual content of the DataRow.

So you need some way to get something bindable out of that parent row. You might create a new DataTable with the same structure and load it with the child rows but that's pretty cumbersome.

If you have a DataRowView, then you can get a bindable DataView out of it using the CreateChildView method. Although you can't lookup or create a DataRowView directly, you can get it by looping over the DefaultView of the associated table. This is how it could roughly look in code:

public DataView GetChildView( DataRow parentRow, DataRelation relation )
{
    // Find the associated DataRowView of the parent data row.
    foreach( DataRowView rowView in parentRow.Table.DefaultView )
    {
        if( rowView.Row == parentRow )
        {
            // Create a child view through the DataRowView and the relation.
            return rowView.CreateChildView( relation );
        }
    }
    
    // No associated DataRowView found.
    return null;
}

Now the resulting DataView can easily be bound to a datagrid.

Friday, January 14, 2005 6:13:38 PM (Romance Standard Time, UTC+01:00) #    Comments [2]  | 

 

Tiny but great DataSet change in Whidbey#

There's a quite lengthy post from the VB team about the data design time changes between Whidbey beta 1 and beta 2 (via 3Leaf Development).

In there, I found a little gem that will solve one particular annoyance I have with the current Typed DataSet generator: while the columns are generated as strongly typed objects, they're declared as internal so they're no use outside the declaring assembly. Hence the column names in quotes in my previous posts about DataSets and Web Services:

testData.Employee.Columns["ID"].ColumnMapping = MappingType.Hidden;
testData.Employee.Columns["CompanyID"].ColumnMapping = MappingType.Hidden;

This will fortunately change in Whidbey, allowing more strongly-typed and thus safer code:

testData.Employee.IDColumn.ColumnMapping = MappingType.Hidden;
testData.Employee.CompanyIDColumn.ColumnMapping = MappingType.Hidden;

I had planned a rant on that but it seems I've been pre-empted by the team that figured it out on their own :-) At least, if the C# team is also doing this but I can hardly imagine that they wouldn't follow the pattern here. It's the small things that can really make a difference you know...

Blog | Programming | .NET | VS.NET | Whidbey | Samples
Thursday, January 13, 2005 10:08:58 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Pushing modified DataSets through Web Services#

There seems to be a bug in the .NET Soap serialization stack when passing a DataSet with an expression column in it that uses a relation. Allow me to elaborate :-) But don't run off just yet if you don't care about the serialization stack, there's some useful stuff in there as well ;-)

Imagine a typed DataSet "TestDataSet" that you're passing from your Service layer to your client UI layer through an ASP.NET Web Service. Say you have a table Employee and a table Company in it, and you want to show the employees in a DataGrid (e.g. WinForms but that doesn't really matter here) with the name of the company in a separate column. The service backend doesn't care about your needs to display the company name inline with the rest of the data, so all you have is the relation CompanyEmployee between the two tables.

For display purposes, you can add a column to the Employee table with its Expression set to "Parent(CompanyEmployee).Name" and it will display just fine: it calculates the new column by navigating to the parent row through the CompanyEmployee relationship and then takes the Name column from that row. Bingo!

TestDataSet testData = TestServiceAgent.GetTestData();
testData.Employee.Columns["ID"].ColumnMapping = MappingType.Hidden;
testData.Employee.Columns["CompanyID"].ColumnMapping = MappingType.Hidden;
testData.Employee.Columns.Add( "CompanyName", typeof( string ), "Parent(CompanyEmployee).Name" );
employeeGrid.DataSource = testData.Employee;

Extra advantages: this value is automatically updated if the parent changes and since it's a calculated column, you can't edit the company name in the grid. Also note that you can hide columns by setting their mapping type to Hidden. That's just a simple trick you can use on the client side.

The problem arises when you use this modified DataSet to update your entity in the backend again: you probably have some SaveTestData method on your service that accepts a TestDataSet to be saved and you just want to pass the changes of the modified DataSet along to it.

TestServiceAgent.SaveTestData( testData.GetChanges() );

Too bad, it will blow up in your face:

Exception: System.Web.Services.Protocols.SoapException: Server was unable to read request. --->
System.InvalidOperationException: There is an error in XML document (1, 12311). --->
System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Data.LookupNode.Bind(DataTable table, ArrayList list)
   at System.Data.DataExpression.Bind(DataTable table)
   at System.Data.DataExpression..ctor(String expression, DataTable table, Type type)
   at System.Data.DataColumn.set_Expression(String value)
   at System.Data.Merger.MergeSchema(DataTable table)
   at System.Data.Merger.MergeTableData(DataTable src)
   at System.Data.Merger.MergeDataSet(DataSet source)
   at System.Data.DataSet.Merge(DataSet dataSet, Boolean preserveChanges, MissingSchemaAction missingSchemaAction)
   at TestProject.TestDataSet.ReadXmlSerializable(XmlReader reader) in C:\TestProject\TestDataSet.cs:line 166
   at System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.ReadXml(XmlReader reader)
   at System.Xml.Serialization.XmlSerializationReader.ReadSerializable(IXmlSerializable serializable)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read14_SaveTestData()
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader)
   at System.Web.Services.Protocols.SoapServerProtocol.ReadParameters()
   --- End of inner exception stack trace ---
   at System.Web.Services.Protocols.SoapServerProtocol.ReadParameters()
   at System.Web.Services.Protocols.WebServiceHandler.Invoke()
   at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()
   at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message,
WebResponse response, Stream responseStream, Boolean asyncCall)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
   at TestProject.TestServiceProxy.SaveTestData(TestDataSet testData) in c:\testproject\testserviceagent.cs:line 291
   at TestProject.TestServiceAgent.SaveTestData(TestDataSet testData) in c:\testproject\testserviceagent.cs:line 162

As you can see, the Soap/XML deserialization stack has some problems with the expression column. And this isn't just because the parent row could be missing in case you only sent the changed rows (which you should do using the GetChanges() method on the DataSet to get the diffgram): it happens even if you send the full DataSet back. If there's an expression column that doesn't use a relation, then there's no problem.

There's a post on .NET 247 called "Serialize DataSet with ExpressionColumn" that shows somebody else running into this issue, and if you kept your foreign languages polished you can also check it out on the Russian GotDotNet site. But that's all I found on it so far, and none offer an explanation or solution.

A possible workaround would be to remove all expression columns again before submitting the DataSet back to the Service layer, but that's pretty harsh, not too transparent, and it can mess up your UI if you're still using the DataSet on-screen.

What I consider to be much better, is to create a new instance of the typed DataSet and merge the changes of the DataSet you're working with into it - making sure you ignore anything not in the DataSet's schema by using MissingSchemaAction.Ignore. This removes all the added clutter you don't need on the backend anyway and it makes sure you get a clean and valid copy of your typed DataSet out of the door.

TestDataSet changes = new TestDataSet();
changes.Merge( testData.GetChanges(), true, MissingSchemaAction.Ignore );
TestServiceAgent.SaveTestData( changes );

This way, everything works and the universe is happy again.

And if you're worried about the cost of the extra DataSet you just created: it's on the client side so I'd consider the performance hit negligible as long as you don't constantly send this type of changes to the backend. Then again, if you are constantly doing this, then there's probably something wrong with the way you're using your service :-p

Wednesday, January 12, 2005 2:31:23 PM (Romance Standard Time, UTC+01:00) #    Comments [5]  | 

 

Poor man's generics in .NET 1.x#

I hadn't seen this little .NET 1.x trick before yet, so I thought I'd share it with you: in case you're really hurting from the lack of generics and you can't wait until sometime next year when VS2005 comes out, here you go... Very poor man's unflexible compile-time generics:

using T = MyItem; // It's magic!

public class PoorMansGenerics
{
    public static T[] CreateArray(int size)
    {
        return new T[size]; // Incredible! Just like real generics!
    }
}

Yeah I know it's lame and it does only 2% of what real generics do, but it can save you some typing and I thought it was pretty funny so whatever :-) For example, for a copy/paste ready solution of strongly typed collections: just replace the MyItem stuff below with whatever you want.

using T = MyItem;

/// <summary>A collection of <see cref="MyItem"/> objects.</summary>
public class MyItemCollection : CollectionBase
{
    /// <summary>
    /// Creates a new <see cref="MyItemCollection"/>
    /// </summary>
    public MyItemCollection()
    {
    }
    
    /// <summary>
    /// Adds the given item.
    /// </summary>
    /// <param name="item">The item to add.</param>
    /// <returns>The position in the list where the item was added.</returns>
    public int Add(T item)
    {
        return List.Add(item);
    }

    /// <summary>
    /// Adds the given items.
    /// </summary>
    /// <param name="items">The items to add.</param>
    public void AddRange(T[] items)
    {
        for (int i = 0; i < items.Length; i++)
        {
            List.Add(items[i]);
        }
    }

    /// <summary>
    /// Inserts a given item.
    /// </summary>
    /// <param name="index">The position where to insert the item.</param>
    /// <param name="item">The item to insert.</param>
    public void Insert(int index, T item)
    {
        List.Insert(index, item);
    }

    /// <summary>
    /// Removes a given item.
    /// </summary>
    /// <param name="item">The item to remove.</param>
    public void Remove(T item)
    {
        List.Remove(item);
    }

    /// <summary>
    /// Determines whether this collection contains the given item.
    /// </summary>
    /// <param name="item">The item to search.</param>
    /// <returns><c>true</c> if this collection contains the given item, <c>false</c> otherwise.</returns>
    public bool Contains(T item)
    {
        return List.Contains(item);
    }

    /// <summary>
    /// Gets the index of the given item in the collection.
    /// </summary>
    /// <param name="item">The item for which to retrieve the index.</param>
    /// <returns>The index of the given item in the collection.</returns>
    public int IndexOf(T item)
    {
        return List.IndexOf(item);
    }

    /// <summary>
    /// Copies the elements in this collection to the given array.
    /// </summary>
    /// <param name="array">The array to which to copy the items.</param>
    /// <param name="index">The index at which to start copying.</param>
    public void CopyTo(T[] array, int index)
    {
        List.CopyTo(array, index);
    }

    /// <summary>
    /// Gets or sets the item at a given index.
    /// </summary>
    public T this[int index]
    {
        get
        {
            return (T)List[index];
        }
        set
        {
            List[index] = value;
        }
    }
}

Blog | Programming | .NET | VS.NET | Samples
Monday, November 29, 2004 3:48:21 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

InfoPathHelper: add offline support to InfoPath!#

If you heard Yves and me talk about InfoPath and SharePoint on the TechNet session a while ago, you'll remember my promise that I'd get my InfoPath helper class I showed there out on the web sometime. Well, I finally got around to building it as v1.0 and wrapping it up nicely so here goes... A big thanks to Ned Friend from Microsoft for the original idea I based this on!

InfoPathHelper is a small reusable .NET library you can use to add offline support to InfoPath forms. That's right, you're finally able to make InfoPath a real smart client that is offline capable of submitting forms and querying data sources, without any extra security requirements whatsoever.

For the end user, the form will appear to continue working when the machine is offline or when the target you're submitting your form to is unavailable (a Web Service, a Sharepoint site, ...). The next time the machine goes online, any pending requests will be submitted. The same goes for querying data sources (files on a network share, Web Services, ...): if they're not available, the data will be taken from the local cache until the next time it can be refreshed.

For a developer using the InfoPath 2003 Toolkit for Visual Studio .NET, it boils down to inheriting from the CachingInfoPathForm base class, changing your submittal options to use form code, deleting the messy generated boilerplate code (you get cleaner properties and events for free in stead) and calling a few methods depending on your needs. The following example shows how to enable offline support for a form that has a submit data adapter named "WebServiceSubmit" and a data object named "Countries".

public class MyCachingForm : CachingInfoPathForm
{
    [InfoPathEventHandler(EventType=InfoPathEventType.OnSubmitRequest)]
    public void OnSubmitRequest(DocReturnEvent e)
    {
        // Attempt to submit the request and return a value to indicate if it worked.
        e.ReturnStatus = ProcessSubmitRequest( "WebServiceSubmit" );
    }

    [InfoPathEventHandler(EventType=InfoPathEventType.OnLoad)]
    public void OnLoad(DocReturnEvent e)
    {
        // Attempt to submit any cached requests and query a data object.
        SubmitCachedRequests( "WebServiceSubmit" );
        QueryDataObject( "Countries" );
    }
}

If you're wondering where the cached data is stored without needing any extra security requirements: welcome to the magical world of Isolated Storage :-)

Although I'm labelling it v1.0, I know there will still be some issues with it. I know of one particular problem I haven't been able to solve (but I suspect it's an InfoPath problem actually): if you populate a drop down list with data from a Web Service and cache that, a COM exception is shown when populating it from the cache saying "This DOM cannot be loaded twice". Any help solving this would be greatly appreciated :-)

Anyway, you're free to use this (as the enclosed license states, usual disclaimers apply) but it would be nice if you'd let me know if you're using it somewhere or if you're trying it out and think it's crap. All feedback is welcome! If there's enough interest in this little project, I might even write an article of some sort to explain the workings and usage a little more in depth than the current documentation file.

Enough talk, here's the goodies:

The JelleDruyts.InfoPathHelper 1.0 library and documentation (123,15 KB)
The JelleDruyts.InfoPathHelper 1.0 library, source code and documentation (218,74 KB)

Monday, November 22, 2004 10:07:41 PM (Romance Standard Time, UTC+01:00) #    Comments [8]  | 

 

Launching the default mail client in .NET#

If you want to launch the default mail client to create a new email message, it's really simple and there's plenty of samples available:

System.Diagnostics.Process.Start( "mailto:user@example.com?subject=TestSubject&body=TestBody" );

But what if you just want to launch the client and nothing more? You'll have to read the registry (remember that you need the proper RegistryPermission for this) to find the default mail client and launch that executable yourself. Here's a method that will do just that:

/// <summary>
/// Launches the default mail client.
/// </summary>
public void LaunchDefaultMailClient()
{
    // Open the "HKLM\SOFTWARE\Clients\Mail" key.
    Microsoft.Win32.RegistryKey mailKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( @"SOFTWARE\Clients\Mail" );

    // The default mail application is stored in the default value of that key.
    string defaultMailApp = (string)mailKey.GetValue( null );

    if( defaultMailApp != null && defaultMailApp.Length > 0 )
    {
        // Open the subkey of the default mail application and the "shell\open\command" key below that.
        Microsoft.Win32.RegistryKey cmdKey = mailKey.OpenSubKey( defaultMailApp + @"\shell\open\command" );

        // We're now in "HKLM\SOFTWARE\Clients\Mail\<default-mail-app>\shell\open\command".
        // The default value of this key is the command line to start.
        string command = (string)cmdKey.GetValue( null );

        // If there are command arguments, extract them out of the main command string.
        string args = string.Empty;
        if( command.IndexOf( " " ) > 0 )
        {
            args = command.Substring( command.IndexOf( " " ) + 1 );
            command = command.Substring( 0, command.IndexOf( " " ) );
        }

        // Start a new process for the mail application.
        System.Diagnostics.Process.Start( command, args );
    }
}

Wednesday, August 25, 2004 6:50:41 PM (Romance Standard Time, UTC+01:00) #    Comments [11]  | 

 

The ASP.NET 2.0 Profile#

Tough luck. I had been busy (well, "busy", as I mentioned: the summer has been a bit of an idle time tech-wise) writing an article on the all-new Profile concept in ASP.NET 2.0, but I just noticed that I've been pre-empted by MSDN where the article "Storing User Information with ASP.NET 2.0 Profiles" just went online. It's a great read though, and it covers pretty much everything I had to say about it (and more of course) except the part about Profile migration on which I'll add my 2 cents now.

Let me just briefly summarize the Profile, which is one of the cornerstones of the new Personalization framework: the Profile is a "personal, type-safe, persistent data store" for each of your website's users. You can use it to remember settings (like language or culture, UI theme, ...), data (favorite color, shopping cart items, ...) or anything else you can think of really. Type-safety is ensured because it is an actual object with typed properties, and the data is automatically persisted into a data store of your choice [1]. Furthermore, you can configure the Profile to work for users that aren't known or logged in yet. When the user decides he likes your site so much that he wants to create an account, you can migrate his anonymous profile very easily in Global.asax by implementing a method like the following one:

Sub Profile_MigrateAnonymous(ByVal s As Object, ByVal e As ProfileMigrateEventArgs)
    
    ' Fish up the profile belonging to the anonymous user that just logged in.
    Dim anonProfile As HttpProfile = Profile.GetProfile(e.AnonymousId)
    
    ' Move the settings over to the authenticated user's profile.
    Profile.FavoriteColor = anonProfile.FavoriteColor

End Sub

So far for the article's example. There's a little issue with this approach though. Imagine a user who already created an account and is now just browsing to your site. He hasn't logged in yet so he gets the default settings for an anonymous user. Then he logs in, thereby migrating and overwriting his own settings (which were persisted from last time) with the default ones. Too bad, you just lost your shopping cart.

Unfortunately, there's currently no way of checking if the profile being migrated to is new - i.e. if the user already had an account and is now just logging in, or if he just created an account. Only in the latter case will you want to migrate the profile, so that's pretty tricky. Hence the following feature suggestion: provide a ProfileMigrateEventArgs.IsProfileNew flag so we can check if we actually need to migrate the profile.

Another option would be to check on the default values of the profile [2]. But what if the user changed one of the properties before he logged in? You would wrongfully not migrate his settings. Not good.

So until there's a better way of doing this, I'd suggest putting a flag on the Profile itself that says it's a new profile and flip it over once you've migrated the anonymous profile, e.g.:

<profile>
 <properties>
  <add name="FavoriteColor" allowAnonymous="true" defaultValue="Red" />
  <add name="HasMigrated" type="System.Boolean" defaultValue="false" />
 </properties>
</profile>

Sub Profile_MigrateAnonymous(ByVal s As Object, ByVal e As ProfileMigrateEventArgs)
    
    ' Migrate the profile preferences only if the user is new.
    If Not Profile.HasMigrated Then
    
        ' Fish up the profile belonging to the anonymous user that just logged in.
        Dim anonProfile As HttpProfile = Profile.GetProfile(e.AnonymousId)
    
        ' Move the settings over to the authenticated user's profile.
        Profile.FavoriteColor = anonProfile.FavoriteColor
        
        ' Indicate that migration has been done.
        Profile.HasMigrated = True

    End If
    
End Sub

One last remark about the Profile: a VaryByProfileProperty option for output caching would make sense. Imagine you have a multilingual website and you keep the user's Culture setting in the Profile. Then you could easily configure your page to be cached for all users that share the same culture as such:

<%@ OutputCache Duration="60" VaryByProfileProperty="Culture"  %>

It's not really shocking but it's just a bit easier than using VaryByCustom and returning the Culture manually...

[1] The Profile derives from the System.Configuration.SettingsBase, which is the new configuration system available in .NET 2.0. It's also used in Windows Forms so you can have type-safe access to application- and usersettings in this environment as well. Goodness!

[2] If you're working in VB.NET, you might wonder how you can access the default value of a Profile property. It's not listed in IntelliSense (I'm still puzzled why not because it does show up if you use C#) but there's a Properties collection you can use to access a certain Profile property and get the default value out of it as follows:

Dim defaultValue As Object = Profile.Properties("FavoriteColor").DefaultValue

Blog | Programming | .NET | ASP.NET | WeFly247 | Whidbey | Samples
Tuesday, August 24, 2004 6:57:30 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Rotating an image around its center in .NET#

As you can see on the new screenshots for the WeFly247.NET Passenger website, there's a dynamic flightmap image displaying the route the plane has already traveled and where the plane is at now.

As I was replacing some hard-coded info (like the image to use for the map, the start- and endpoints of the flight on the map, ...), I figured I was also going to need to rotate the plane dynamically so that it would follow the path (I wouldn't feel too comfortable if I was looking at a flightmap where my plane seemed to fly sideways). Before that, I had just rotated the static image itself, but if the flight path can change, so can the rotation.

There is an Image.RotateFlip method but that only allows rotating over 90/180/270 degrees. I also looked at some matrix transformations on a Region object using Matrix.Rotate, but that seemed a little too hard to get working. Furthermore, I wanted to rotate the plane around its center (not around the upper left corner as most rotations do) so I sat down and thought deeply about rotation equations (so my math teacher was right, it does come in handy sometimes!), decided I forgot all about it (sorry teach), and looked it up again.

After rotating a point (x, y) around the origin over an angle a, the new coordinates (x', y') are:

x' = x cos(a) + y sin(a)
y' = -x sin(a) + y cos(a)

Now you can use a Graphics.DrawImage method overload to draw the image in a given parallelogram extrapolated from an array of three points. That would do just fine to rotate the image if I could calculate the three points, which I could using the simple formulas above. So my final implementation to draw a rotated image onto a graphics object became (beware: VB.NET snippet coming up ;-) ):

' Draws an image onto the given graphics object. The image is rotated by a specified angle
' (in radians) around its center and then drawn at the given center point.
Private Sub DrawImageRotatedAroundCenter(ByVal g As Graphics, _
    ByVal center As Point, ByVal img As Image, ByVal angle As Double)

    ' Think of the image as a rectangle that needs to be drawn rotated.
    ' Rotate the coordinates of the rectangle's corners.
    Dim upperLeft As Point = RotatePoint(New Point(-img.Width / 2, img.Height / 2), angle)
    Dim upperRight As Point = RotatePoint(New Point(img.Width / 2, img.Height / 2), angle)
    Dim lowerLeft As Point = RotatePoint(New Point(-img.Width / 2, -img.Height / 2), angle)

    ' Create the points array by offsetting the coordinates with the center.
    Dim points() As Point = {upperLeft + center, upperRight + center, lowerLeft + center}
    
    ' Draw the rotated image.
    g.DrawImage(img, points)

End Sub

' Rotates a point around the origin by the specified angle (in radians).
' Rotation adheres to the following rules for the new coordinates:
' x' = x cos(a) + y sin(a)
' y' = -x sin(a) + y cos(a)
Private Function RotatePoint(ByVal p As Point, ByVal angle As Double) As Point

    Dim x As Integer = p.X * Math.Cos(angle) + p.Y * Math.Sin(angle)
    Dim y As Integer = -p.X * Math.Sin(angle) + p.Y * Math.Cos(angle)

    Return New Point(x, y)

End Function

Wednesday, May 26, 2004 9:53:56 AM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Working around some ASP.NET AdRotator limitations#

I'm playing working with the updated AdRotator in ASP.NET 2.0, and I had to work around some limitations so I figured I'd best share what I came up with...

The AdRotator is a control that shows a random advertisement, which is basically just an image and a link to a target page (like a shopping page or an e-commerce site). And that's the problem. That's all it is. An image and a link. What I wanted to do is make an advertisement that also shows a description and a price next to it, but unfortunately there's no templating mechanism for the AdRotator to customize the control. (Spot the hidden feature request.)

Another problem I had is that I pull the advertisement data from the database (the new AdRotator supports the cool new ASP.NET 2.0 databinding so you're not limited to xml files anymore like before). But there's no NavigateUrl in the database, and I need to give the AdRotator a URL to which it should navigate when you click the image. Unfortunately, I can't make the NavigateUrlField "smart". I have a ProductID in my datasource, so I'd like to have it construct a url based on that, a little something like this:

<asp:AdRotator (...) NavigateUrlField='<%# Eval("ProductID", "~/Shop.aspx?ProductID={0}") %>' />

But that doesn't work of course, the NavigateUrlField has to point to a field in the datasource (and I'm not sure if this <%# %> construct would work anyway)...

I could add a NavigateUrl field on the business tier but frankly, I don't want to. It's not the business tier's business (pun intended) to know there's an AdRotator somewhere down the line that needs this funny Shopping URL. It only knows about products. I could add the column on the client tier but then I'd lose my beautifully simple databinding since I'd need to preprocess the data. Well in fact the databinding would still be easy, I'd just bind it to a local "proxy" preprocessor object in stead of to the actual webservice. But it's less transparant and I still wouldn't have my price and description anywhere near my image...

In the end, I decided to do it a little differently. I created a simple Advertisements user control with an AdRotator in it, and labels for the price and description. Then I just use the AdRotator's AdCreated event to play around a little bit. The trick is, you can get to the data source fields through the AdProperties dictionary on the AdCreatedEventArgs. I'll just let the code speak for itself:

<%@ control language="VB" classname="Advertisement"%>
<script runat="server">
 Private Sub shopAds_AdCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.AdCreatedEventArgs)
  e.NavigateUrl = "~/Shop.aspx?ProductID=" & e.AdProperties("ProductID")
  description.Text = e.AdProperties("Description")
  price.Text = e.AdProperties("Price")
 End Sub
</script>
<table>
 <tr>
  <td>
   <asp:AdRotator ID="shopAds" Runat="server" DataSourceID="advertisementData" ImageUrlField="ImageUri" OnAdCreated="shopAds_AdCreated" />
  </td>
  <td>
   <asp:Label ID="description" Runat="Server" />
   <br />
   <asp:Label ID="price" Runat="Server" />
  </td>
 </tr>
</table>
<asp:ObjectDataSource ID="advertisementData" Runat="server" TypeName="WeFly247.UI.Proxies.PassengerWebService" SelectMethod="GetProducts">
 <SelectParameters>
  <asp:ProfileParameter Name="cultureName" Type="String" PropertyName="Culture" />
 </SelectParameters>
</asp:ObjectDataSource>
Blog | Programming | .NET | ASP.NET | WeFly247 | Whidbey | Samples
Wednesday, April 28, 2004 10:14:37 AM (Romance Standard Time, UTC+01:00) #    Comments [13]  | 

 

DataBinding in ASP.NET 2.0#

ASP.NET 2.0 features a whole new DataBinding infrastructure, which allows you to define and consume data sources declaratively. Sounds fancy, and it is. If you look at last week's coding summary, you can see this in action:

<asp:ObjectDataSource ID="infoData" Runat="server" TypeName="WeFly247.UI.Proxies.PassengerWebService" SelectMethod="GetFlightData" />
<asp:GridView ID="info" Runat="Server" DataSourceID="infoData" />

As you can see, there's that new GridView control, and in stead of binding it in code (you know, set the DataSource property and don't forget to call DataBind!) you just set a DataSourceID. This is a reference to some data source object, which can now be just a control as any other (under the "more markup! less code!" motto).

Now there are a number of data source controls: SqlDataSource (accesses SQL Server), AccessDataSource (accesses Access (whew)), SiteMapDataSource (reads a sitemap file), DataSetDataSource and XmlDataSource (which speak for themselves), and last but certainly not least: the ObjectDataSource.

Now this is what I call goodness. They've finally come to their senses and realized that not everyone uses a database on the client tier. They've heard themselves say "Web Services" one time too many I guess, and this ObjectDataSource is the solution for everyone who has data living on another tier. What it does is create a new instance of the type you specify, and then call a method on it to get the data. After that, the object is disposed again but your disconnected data remains alive in the data source control.

Possibly (I'm just guessing - hoping actually - here, I haven't checked since I can't use Reflector to decompile this yet :-) ) they'll cache the object and method information (not the object itself, the documentation states clearly that it's disposed of) somehow to prevent every call from having the overhead of using reflection to find the type and the method to call. Anyway.

So in our case this little piece of markup just creates a new Web Service proxy for the passenger Web Service, calls the GetFlightData method on it (which effectively sets the whole SOAP story in action, talk about hidden complexity) and uses the returned data for data binding.

Sounds easy, and easy usually means limited. Fortunately, they've thought well about it, and provided quite a range of extra options. DataSource controls support parameters to be passed in or out, through parameter controls which can fetch their values from other controls, form fields, cookies, the new ASP.NET Profile, the query string, the session, or you can build your own. (A literal parameter value would be nice to pass in a constant though, but it doesn't seem to be there...). For example, this one fetches the user's culture from his Profile, and passes that along to the GetProducts method:

<asp:ObjectDataSource ID="advertisementData" Runat="server" TypeName="WeFly247.UI.Proxies.PassengerWebService" SelectMethod="GetProducts">
 <SelectParameters>
  <asp:ProfileParameter Name="cultureName" Type="String" PropertyName="Culture" />
 </SelectParameters>
</asp:ObjectDataSource>

And in case you were worried about two-way data, DataSource controls also support updating, deleting and inserting through other properties - again with parameters like before. For a lot more information, look at the excellent MSDN article on databinding in ASP.NET Whidbey by Early & Adopter.

So this is one of the reasons I'm pretty impressed with ASP.NET 2.0. More reasons are bound to follow - stay tuned :-)

Wednesday, April 28, 2004 9:59:20 AM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

OneShotFlag#

If you've ever written some test code before (you do write unit tests, don't you?), you probably ran into the common code construct that you initialize a "success" flag to a certain value so you can toggle its state later on. I just wrote a little utility class to guard you from making mistakes here: a boolean-like flag that can only change its state (value) once.

Normally, you would initialize a boolean flag to the expected outcome and then only change that flag if one of the tests failed. You can't set the flag directly everytime because that can reset the value when it should not be reset (e.g. if one test succeeds then the failure of the previous tests would be "forgotten").

Bad code (the result of the first test won't ever matter):

bool success = true; // We expect this to work.
success = !HasFirstTestSucceeded();
success = !HasSecondTestSucceeded();
// ...
if( success ) Party();

Better code (semantically correct but cumbersome):

bool success = true; // We expect this to work.
if( !HasFirstTestSucceeded() ) success = false; // Only change the value if necessary.
if( !HasSecondTestSucceeded() ) success = false;
// ...
if( success ) Party();

So you have to write code that only changes the flag if it differs from its initial state. This is just where the OneShotFlag class helps: it only allows the flag to be changed only once. Furthermore, it can implicitly be converted to a boolean so checking its state is just as easy.

But it becomes more natural like this:

OneShotFlag success = new OneShotFlag( true ); // We expect this to work.
success.State = HasFirstTestSucceeded(); // Change the value directly.
success.State = HasSecondTestSucceeded();
// ...
if( success ) Party(); // Note the implicit conversion to a boolean here!

Here's the code for the class:

public class OneShotFlag
{
 /// <summary>
 /// The current state of the flag.
 /// </summary>
 private bool m_State;
 /// <summary>
 /// Indicates if the flag has already changed its value once.
 /// </summary>
 private bool m_ShotFired;
 /// <summary>
 /// Creates a new <see cref="OneShotFlag"/>.
 /// </summary>
 /// <param name="initialState">The initial <see cref="State"/> of the flag.</param>
 public OneShotFlag( bool initialState )
 {
  m_ShotFired = false;
  m_State = initialState;
 }
 /// <summary>
 /// Gets or sets the current state of the flag. The state will effectively be changed only once.
 /// </summary>
 public bool State
 {
  get
  {
   return m_State;
  }
  set
  {
   if( m_State != value && !m_ShotFired )
   {
    lock( this )
    {
     m_State = value;
     m_ShotFired = true;
    }
   }
  }
 }
 /// <summary>
 /// Requests to change the state. The state will effectively be changed only once.
 /// </summary>
 public void ToggleState()
 {
  State = !State;
 }
 /// <summary>
 /// Implicitly converts the given OneShotFlag to a boolean value.
 /// </summary>
 /// <param name="flag">The OneShotFlag to convert.</param>
 /// <returns>The current <see cref="State"/> of the OneShotFlag.</returns>
 public static implicit operator bool( OneShotFlag flag )
 {
  return flag.State;
 }
}

Sunday, March 14, 2004 11:40:01 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Cursors#

If you've ever written a client app with some time consuming code, you probably wanted to show the obligatory hourglass at some point. This is not a hard thing to do in .NET, but you should be careful to do it right.

One way would be to just do this (in some method of a form or control):

this.Cursor = Cursors.WaitCursor;
// time consuming code...
this.Cursor = Cursors.Default;

However, this can result in an hourglass that never goes away if your time consuming code decided to fail and bail out with an exception.

This looks better:

try
{
   this.Cursor = Cursors.WaitCursor;
   // time consuming code...
}
finally
{
   this.Cursor = Cursors.Default;
}

But this approach has one minor drawback: you can't stack operations on top of each other. If this code got called from some other time consuming code which had already set the hourglass, you're now falsely telling the user that it has already finished.

What you should do is remember the cursor as it is before you're changing it, and restoring it at the end. This can get pretty boring copy/paste code if you need to do this a lot so I wrote a little helper class which wraps this up for you. Your code can now be as elegant as:

using( new CursorHelper( this ) )
{
   // time consuming code...
}

Here's the code for the helper class. It just remembers the current state, and restores it when disposed. You can also use an overloaded constructor to use other cursors than the hourglass by the way. It could use some more defensive code (like checking if the control isn't null or already disposed etc.) but it gives you a good idea...

/// <summary>
/// A helper object that allows to set and restore the cursor on a control easily.
/// </summary>
/// <example>
/// This will create a WaitCursor on a control and restore it to the previous state after the "using" block:
/// <code>
///   using( new CursorHelper( this ) )
///   {
///      // time consuming code...
///   }
/// </code>
/// </example>
public class CursorHelper : IDisposable
{
   /// <summary>
   /// The previous cursor of the associated control.
   /// </summary>
   private Cursor m_OldCursor;
   /// <summary>
   /// The control of which to set the cursor.
   /// </summary>
   private Control m_Control;
   /// <summary>
   /// Creates a new CursorHelper for the given control. Sets the current cursor to
   /// a WaitCursor and restores the previous cursor on Dispose or destruction.
   /// </summary>
   /// <param name="ctrl">The control of which to set the cursor.</param>
   public CursorHelper( Control ctrl )
   {
      m_OldCursor = ctrl.Cursor;
      m_Control = ctrl;
      ctrl.Cursor = Cursors.WaitCursor;
   }
   /// <summary>
   /// Creates a new CursorHelper for the given control. Sets the current cursor to
   /// the given cursor and restores the previous cursor on Dispose or destruction.
   /// </summary>
   /// <param name="ctrl">The control of which to set the cursor.</param>
   /// <param name="newCursor">The new cursor to set.</param>
   public CursorHelper( Control ctrl, Cursor newCursor )
   {
      m_OldCursor = ctrl.Cursor;
      m_Control = ctrl;
      ctrl.Cursor = newCursor;
   }
   /// <summary>
   /// Restores the cursor on the associated control.
   /// </summary>
   public void Dispose()
   {
      m_Control.Cursor = m_OldCursor;
      GC.SuppressFinalize( this );
   }
   /// <summary>
   /// Restores the cursor on the associated control.
   /// </summary>
   ~CursorHelper()
   {
      Dispose();
   }
}
Wednesday, November 12, 2003 3:58:14 PM (Romance Standard Time, UTC+01:00) #    Comments [3]  | 

 

MenuItems#

In Windows Forms, you can only add strings to be displayed in a menu. It would be a lot easier if you could add entire objects to a MenuItem, which could override their ToString()-method to determine the way to represent them in the menu item. For instance, you may want to map menu items to objects so that when the menu item is clicked, you immediately get the correct object in the event handler's sender object. Now, there is no way to associate an object with a menu item.

As a solution, you could consider subclassing the MenuItem class and adding a Tag property that can contain any Object. It is impossible to override the Text property of the MenuItem class so you must set the Text property to the tag object's ToString() value at construction or when the Tag property changes. Now when you listen for the Click event of the MenuItem, cast the sender to your MenuItem subclass and obtain the associated object through the Tag property.

Code sample for a generic MenuItem class with a Tag property:

public class TaggedMenuItem : System.Windows.Forms.MenuItem
{
    private object _tag;
    public object Tag
    {
        get { return _tag; }
    }
    public TaggedMenuItem( object tag )
    {
        _tag = tag;
        if( tag != null ) this.Text = tag.ToString();
    }
}

Of course you can do a whole lot more with this kind of subclassing but this can serve as an easy start...

Monday, October 20, 2003 10:20:04 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

All content © 2010, Jelle Druyts
On this page
Recent Photos
www.flickr.com
This is a Flickr badge showing public photos from Jelle Druyts. Make your own badge here.
Advertising
Top Picks
Statistics
Total Posts: 346
This Year: 0
This Month: 0
This Week: 0
Comments: 525
Archives
Sitemap
Disclaimer
This is my personal website, not my boss', not my mother's, and certainly not the pope's. My personal opinions may be irrelevant, inaccurate, boring or even plain wrong, I'm sorry if that makes you feel uncomfortable. But then again, you don't have to read them, I just hope you'll find something interesting here now and then. I'll certainly do my best. But if you don't like it, go read the pope's blog. I'm sure it's fascinating.

Powered by:
newtelligence dasBlog 2.0.7226.0

Sign In